---
title: CSS2DRenderer Overlay
---

This example shows how to run an additional Three.js renderer in parallel with
Threlte's `<Canvas>`, while still leveraging Threlte's built-in elements.
Specifically, we'll run
[CSS2DRenderer](https://threejs.org/docs/index.html#examples/en/renderers/CSS2DRenderer)
to add flat labels to objects in a three-dimensional scene.

This example can be easily adapted to use
[CSS3DRenderer](https://threejs.org/docs/index.html#examples/en/renderers/CSS3DRenderer)
instead, if you want the elements to live "inside" the scene, rather than flat
across the surface.

<Example path="renderers/CSS2DRenderer" />

## How does it work?

In this scene, we run two renderers - the default one provided by Threlte, and a
new CSS2DRenderer which we initialize manually. Threlte's renderer runs on a
canvas element as usual, while our new renderer runs in a `<div>` with absolute
positioning on top of it.

### The render loop

To integrate the a new renderer into svelte's loop, we call it inside a task
added right _after_ [Threlte's
`autoRenderTask`](/docs/learn/basics/scheduling-tasks#default-tasks). For
details on how to use the _Threlte Task Scheduling System_, see the
[documentation](/docs/learn/basics/scheduling-tasks).

By default, each renderer traverses the scene and updates every object. We can
set
[scene.matrixWorldAutoUpdate](https://threejs.org/docs/#api/en/core/Object3D.matrixAutoUpdate)
to false and manually call `scene.updateMatrixWorld()` each tick in order to
avoid duplicating the work, since we're running two renderers. To do that, we're
adding a task that runs right _before_ Threlte's `autoRenderTask`.

```svelte title="Scene.svelte"
<script>
  const { scene, size, autoRenderTask, camera } = useThrelte()

  // Set up the CSS2DRenderer to run in a div placed atop the <Canvas>
  const element = document.querySelector('#css-renderer-target') as HTMLElement
  const cssRenderer = new CSS2DRenderer({ element })
  $effect(() => {
    cssRenderer.setSize($size.width, $size.height)
  })

  // We are running two renderers, and don't want to run
  // updateMatrixWorld twice; tell the renderers that we'll handle
  // it manually.
  // https://threejs.org/docs/#api/en/core/Object3D.updateWorldMatrix
  scene.matrixWorldAutoUpdate = false

  // To update the matrices *once* per frame, we'll use a task that is added
  // right before the autoRenderTask. This way, we can be sure that the
  // matrices are updated before the renderers run.
  useTask(
    () => {
      scene.updateMatrixWorld()
    },
    { before: autoRenderTask }
  )

  // The CSS2DRenderer needs to be updated after the autoRenderTask, so we
  // add a task that runs after it.
  useTask(
    () => {
      // Update the DOM
      cssRenderer.render(scene, camera.current)
    },
    {
      after: autoRenderTask,
      autoInvalidate: false
    }
  )
</script>
```

### Setting up CssObject

The other integral part is a component that accepts DOM contents in the default
slot and places them in the scene and renders them with the ThreeJS
`CSS2DRenderer`:

```svelte title="CssObject.svelte"
<script>
  import { T } from '@threlte/core'
  import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js'

  let { pointerEvents = false, children, ...rest } = $props()

  let element = $state()
</script>

<div
  bind:this={element}
  style:pointer-events={pointerEvents ? 'auto' : 'none !important'}
  style:will-change="transform"
>
  {@render children?.()}
</div>

{#if element}
  <T
    is={CSS2DObject}
    args={[element]}
    {...rest}
  />
{/if}
```

This component renders children into a div, and allows nested Threlte components
via the `three` slot. It passes all other properties through, letting us use it
like so:

```svelte
<CssObject
  position={[-1, 2, 1]}
  center={[-0.2, 0.5]}
>
  <CounterLabel label="Hello" />

  <T.Mesh slot="three">
    <T.SphereGeometry args={[0.25]} />
    <T.MeshStandardMaterial color="#4F6FF6" />
  </T.Mesh>
</CssObject>
```

where `<CounterLabel>` is a normal Svelte component outside Threlte's control,
but the mesh is a component inside the scene hooked in with `slot="three"`.
